﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Interface;

namespace RayDen.RayEngine.Managers
{
    public class AnimationManager
    {
        private RayEngineScene scene;
        //private int currentFrame, maxFrames;
        private List<ObjectModifier> modifiers;


        public AnimationManager(RayEngineScene scn, int framesCount)
        {
            this.scene = scn;
           // this.currentFrame = 0;
            //this.maxFrames = framesCount;
            this.modifiers = new List<ObjectModifier>(10);
            this.Populate(framesCount);
        }

        private void Populate(int framesCount)
        {
            this.modifiers.Add(new CameraModifier(scene, framesCount));
        }


        public void Apply(int frameIndex)
        {
            foreach (var objectModifier in modifiers)
            {
                objectModifier.Apply(frameIndex);
            }
        }


        public class InterpolatedVectorValue : InterpolatedValue<Vector>
        {
            public InterpolatedVectorValue(int steps, ref Vector start, ref Vector end)
                : base(steps, ref start, ref end)
            {
            }

            public override Vector GetValue(int step)
            {
                var t = (step / (float)StepsCount);
                var v = EndValue - StartValue;
                return (StartValue + ((t / v.Length) * v));
            }
        }
    }

    public class CameraModifier : ObjectModifier
    {
        private InterpolatedPointValue cameraPosition;
        private AnimationManager.InterpolatedVectorValue cameraDirection;
        private InterpolatedFloatValue cameraFov;

        public CameraModifier(RayEngineScene scn, int frames) : base(scn)
        {
            var fov0 = scn.Camera.Fov;
            var fov1 = scn.Camera.Fov + 20f;
            this.cameraFov = new InterpolatedFloatValue(frames,ref fov0, ref fov1);

            var pos0 = this.scene.Camera.Position;

            var pos1 = this.scene.Camera.Position + this.scene.Camera.Target * ((this.scene.Camera.Target-this.scene.Camera.Position.ToVec()).Length/frames);

            this.cameraPosition = new InterpolatedPointValue(frames, ref pos0, ref pos1);
        }

        public override void Apply(int frameIndex)
        {
            //scene.Camera.Fov = cameraFov.GetValue(frameIndex);
            this.scene.Camera.Position = this.cameraPosition.GetValue(frameIndex);
        }
    }

    public class InterpolatedRgbSpectrumValue : InterpolatedValue<RgbSpectrum>
    {
        public InterpolatedRgbSpectrumValue(int steps, ref RgbSpectrum start, ref RgbSpectrum end)
            : base(steps, ref start, ref end)
        {
        }

        public override RgbSpectrum GetValue(int step)
        {
            var t = (step / (float)this.StepsCount);
            return (1f - t) * this.StartValue + t * this.EndValue;
        }
    }

    public class InterpolatedPointValue : InterpolatedValue<Point>
    {
        public InterpolatedPointValue(int steps, ref Point start, ref Point end)
            : base(steps, ref start, ref end)
        {
        }

        public override Point GetValue(int step)
        {
            var t = (step / (float)this.StepsCount);
            var v = this.EndValue - this.StartValue;
            return (this.StartValue + ((t) * v));
        }
    }

    public class InterpolatedFloatValue : InterpolatedValue<float>
    {
        public InterpolatedFloatValue(int steps, ref float start, ref float end)
            : base(steps, ref  start, ref end)
        {
        }

        public override float GetValue(int step)
        {
            return MathLab.Lerp((step / (float)this.StepsCount), this.StartValue, this.EndValue);
        }
    }

    public abstract class InterpolatedValue<TValue>
        where TValue : struct
    {
        public int StepsCount;

        public TValue StartValue;
        public TValue EndValue;


        protected InterpolatedValue(int steps, ref TValue start, ref TValue end)
        {
            this.StepsCount = steps;
            this.StartValue = start;
            this.EndValue = end;
        }

        public abstract TValue GetValue(int step);
    }

    public abstract class ObjectModifier
    {
        protected RayEngineScene scene;

        protected ObjectModifier(RayEngineScene scn)
        {
            this.scene = scn;
        }
        public ILight FindLight(string name)
        {
            return
                this.scene.Lights.SingleOrDefault(
                    item => item.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));

        }

        public ITriangleMesh FindMesh(string name)
        {
            return
                this.scene.Meshes.SingleOrDefault(
                    item => item.MeshName.Equals(name, StringComparison.InvariantCultureIgnoreCase));
        }

        public abstract void Apply(int frameIndex);
    }
}
